
using System;
using System.Collections.Generic;
using System.Text;
using Calc.Operations;
using Calc.Parser;

namespace Calc.Execution
{
	public class ExecutionEngine
	{
		public const string ANSWER_VARIABLE = "ANSWER";
		protected ExecutionContext _context;
		protected List<ExecutionResult> _results;

		public ExecutionEngine()
		{
			_context = new ExecutionContext();
			_context.InitBuiltInFunctions();
			_context.InitBuiltInConstants();

			_results = new List<ExecutionResult>();
		}

		#region Context Delegates
		/// <summary>
		/// Sets the value for a given variable
		/// </summary>
		/// <param name="name">The name of the variable</param>
		/// <param name="value">The value to be assigned</param>
		/// <returns>The previous value of the variable, if the specified variable 
		/// already existed; otherwise double.MinValue.</returns>
		public double SetVariable(string name, double value)
		{
			return _context.SetVariable(name, value);
		}

		/// <summary>
		/// Retrieves the value for a given variable
		/// </summary>
		/// <param name="name">The name of the variable</param>
		/// <returns>The value for the specified variable, or double.MinValue if
		/// that variable is undefined</returns>
		public double GetVariable(string name)
		{
			return _context.GetVariable(name);
		}

		/// <summary>
		/// Retrieves whether or not a given variable exists
		/// </summary>
		/// <param name="name">The name of the variable</param>
		/// <returns>true if the variable is declared; otherwise false</returns>
		public bool IsVariableDeclared(string name)
		{
			return _context.IsVariableDeclared(name);
		}

		/// <summary>
		/// Adds a new function to the running context, or overrides and existing function
		/// </summary>
		/// <param name="function"></param>
		public void CreateFunction(Function function)
		{
			_context.CreateFunction(function);
		}

		/// <summary>
		/// Checks whether or not a function is defined
		/// </summary>
		/// <param name="function">The function call to check</param>
		/// <returns>True if the function is defined, false otherwise</returns>
		public bool IsFunctionDefined(Function function)
		{
			return _context.IsFunctionDefined(function);
		}

		/// <summary>
		/// Checks whether or not a function is defined
		/// </summary>
		/// <param name="name">The name of the function to check</param>
		/// <returns>True if the function is defined, false otherwise</returns>
		public bool IsFunctionDefined(string name)
		{
			return _context.IsFunctionDefined(name);
		}

		/// <summary>
		/// Returns the result of executing the given function within a local context
		/// </summary>
		/// <param name="function"></param>
		/// <returns></returns>
		public double ExecuteFunction(Function function)
		{
			return _context.ExecuteFunction(function);
		}

		/// <summary>
		/// Returns the value of the ANSWER variable
		/// </summary>
		public double Answer
		{
			get { return _context.Answer; }
		}

		public Dictionary<string, double> Variables
		{
			get { return _context.Variables; }
		}

		public Dictionary<string, Function> Functions
		{
			get { return _context.Functions; }
		}
		#endregion

		public ExecutionResult ExecuteLine(string line)
		{
			ExecutionResult res = ExecuteExclusiveLine(line);

			_results.Add(res);
			if ( res.Result == ExecutionType.Eval || res.Result == ExecutionType.VariableAssignment )
			{
				SetAnswerVariable(res.Value);
			}

			return res;
		}

		public ExecutionResult ExecuteExclusiveLine(string line)
		{
			string message = "";
			double value = 0.0d;
			ExecutionType result = ExecutionType.None;

			IOperand op = null;

			try
			{
				op = EquationParser.Parse(line);
			}
			catch ( EquationParserException epe )
			{
				message = epe.Message;
				result = ExecutionType.Error;

				return new ExecutionResult(line, message, result, value);
			}

			try
			{
				if ( op is BinaryOperator )
				{
					if ( op is AssignmentOperator )
					{
						return ProcessAssignment((AssignmentOperator)op);
					}
					else
					{
						return ProcessOperator(line, (IOperator)op);
					}
				}
				else if ( op is Function )
				{
					double val = _context.ExecuteFunction((Function)op);

					return new ExecutionResult(line, val.ToString(), ExecutionType.Eval, val);
				}
				else if ( op is IOperator )
				{
					return ProcessOperator(line, (IOperator)op);
				}
				else
				{
					double val = op.GetValue(_context);

					return new ExecutionResult(line, val.ToString(), ExecutionType.Eval, val);
				}
			}
			catch ( ExecutionException eex )
			{
				message = eex.Message;
				result = ExecutionType.Error;

				return new ExecutionResult(line, message, result, value);
			}
			catch ( Exception ex )
			{
				message = ex.Message;
				result = ExecutionType.Error;

				return new ExecutionResult(line, message, result, value);
			}
		}

		protected ExecutionResult ProcessOperator(string line, IOperator oper)
		{
			string message;
			ExecutionType result;
			CalcResult calc;
			calc = oper.Calc(_context);

			if ( calc.ResultType == CalcResultType.Error )
			{
				return CreateError(calc, line);
			}
			else if ( calc.ResultType == CalcResultType.Boolean )
			{
				message = calc.BooleanResult.ToString();
				result = ExecutionType.Eval;

				return new ExecutionResult(line, message, result, calc.DecimalResult);
			}
			else
			{
				message = calc.DecimalResult.ToString();
				result = ExecutionType.Eval;

				return new ExecutionResult(line, message, result, calc.DecimalResult);
			}
		}

		protected ExecutionResult ProcessAssignment(AssignmentOperator assOp)
		{
			string line = "";
			string message = "";

			ExecutionType result = ExecutionType.None;
			CalcResult calc;

			// execute via Calc
			calc = assOp.Calc(_context);

			if ( calc.ResultType == CalcResultType.Error )
			{
				return CreateError(calc, line);
			}
			else
			{
				if ( assOp.LeftSide is Function )
				{
					message = "The Function " + assOp.RightSide.ToString();
					result = ExecutionType.FunctionAssignment;
				}
				else if ( assOp.LeftSide is Variable )
				{
					message = "The Variable " + assOp.RightSide.ToString();
					result = ExecutionType.VariableAssignment;
				}
				message += " was created";

				return new ExecutionResult(line, message, result, calc.DecimalResult);
			}
		}

		protected void SetAnswerVariable(double value)
		{
			_context.SetVariable(ANSWER_VARIABLE, value);
		}

		private static ExecutionResult CreateError(CalcResult calc, string line)
		{
			string message;
			ExecutionType result;
			message = calc.ErrorMessage;
			result = ExecutionType.Error;

			return new ExecutionResult(line, message, result, calc.DecimalResult);
		}
	}
}
